By: Allyson Lynch
import pandas as pd
import csv
Pull the name of the amine(s) in a CCDC crystal
#helper functions to check amine name
def not_metal(c):
metals_df = pd.read_table("Outputs/01 CCDC Smiles File/Input Files/Metals.tsv")
metals=[True if c.find(metal.lower())==-1 else False for metal in metals_df.columns[1:]]
return all(metals)
def is_strict_organic(c):
stricts = ["cyano", "fluoro", "chlor", "bromo", "iod", "bor", "ars", "nitro", "nitra", "nitrile", "ars", "sil", "phosph", "sulf", "sel", "thio", "thia", "thie", "oxa", "oxy", "none", "dimetham", "glycol", "azido", "oxide"]
organic = [True if c.find(strict)==-1 else False for strict in stricts]
return all(organic)
def is_amine(c):
ams = ["ium", "amin", "ammon", "ine", "ammin", "az", "pyrimid", "nitrilo", "phyrin", "pyrrol", "isoindole", "anil", "ptilocaulin", "phycene", "annulene", "metformin", "pyrid"]
amines = [False if c.find(am)!=-1 else True for am in ams]
if all(amines)!=True:
return True
#helper functions to clean amine name
def reformat(mlc):
for symbol in ["!", "$", "catena"]: #clean extra symbols
mlc = mlc.replace(symbol, "")
if mlc[0]=="-":
mlc = mlc.replace("-", "", 1)
if mlc.find("isosparteine")==-1:
for descriptors in ["(r)-", "r-", "(s)-", "-s-", "(s/r)-", "(r/s)-", "(r,r)-", "(r,s)-", "rac-", "rctt-", "(1r,5s)-", "(+)-", "(-)-", "(+-)-", "trans-", "cis-", "trans,", "cis,", "iv-", "(l)-", "(d)-", "meso-", "anti-", "syn-", "hemi-", "endo-", "hemi(", "rtct-", "alpha-", "beta-"]:
mlc = mlc.replace(descriptors, "")
if mlc.find("dec-")==-1:
mlc = mlc.replace("c-", "")
while (mlc.count("(")>mlc.count(")")) or (mlc.count("[")>mlc.count("]")): #adjust parenthesis (remove uneven and non-internal)
mlc = mlc[1:]
while (mlc.count("(")<mlc.count(")")) or (mlc.count("[")<mlc.count("]")):
mlc = mlc[:-1]
if ((mlc.rfind("(")!=mlc.find("(")) or mlc.count("(")==1) or ((mlc.rfind("[")!=mlc.find("[")) or mlc.count("[")==1):
while mlc[0]=="(" and mlc[-1]==")":
mlc = mlc[1:-1]
prefixes_df = pd.read_table("Outputs/01 CCDC Smiles File/Input Files/Prefixes.tsv")
prefixes = prefixes_df.columns[1:].tolist()
prefixes.append("bis")
for prefix in prefixes: #remove extra prefixes
while mlc.find(prefix)!=-1 and ((mlc.find(prefix)+len(prefix))==mlc.find("(") and mlc.rfind(")")+1==len(mlc)) or ((mlc.find(prefix)+len(prefix))==mlc.find("[") and mlc.rfind("]")+1==len(mlc)):
mlc = mlc.replace(prefix, "", 1)
if (mlc[0]=="(" and mlc[-1]==")") or (mlc[0]=="[" and mlc[-1]=="]"): #fix parenthesis
mlc = mlc[1:-1]
if mlc[:2]=="s-":
mlc = mlc.replace("s-", "")
for chirality in ["rs", "sr", "ars", "ar", "br"]: #remove chirality
if mlc.find(chirality)!=-1 and (mlc[mlc.index(chirality)-1].isdigit() or mlc[mlc.index(chirality)-1]=="("):
mlc = mlc[mlc.index(")-")+2:]
for n,N in [["n-", "N-"], ["n,", "N,"], ["n'", "N'"], ["n6", "N6"]]: #recapitalize Nitrogen
if mlc.find("tetra-n-butylammonium")==-1 and mlc.find("tetra-n-propylammonium")==-1:
mlc = mlc.replace(n,N)
for i in range(1,10):
if mlc.find("tetra-n-butylammonium")==-1 and mlc.find("tetra-n-propylammonium")==-1:
mlc = mlc.replace("n"+str(i), "N"+str(i))
return mlc
def neutralize(comp):
if comp.find("methyl")==-1 and comp.find("diethyl")==-1 and comp.find("tri")==-1:
for ethyl in ["ethylene-1,2-diammonium","ethylene-1.2-diammonium", "1,2-ethylenediammonium", "ethylene-1,2-diamine", "1,2-ethylenediamine", "1,2-ethyldiammonium", "N,N'-ethylenediamine", "ethylenediamine", "2-aminoethylammonium", "1-2,diaminoethane"]:
comp = comp.replace(ethyl,"ethane-1,2-diamine")
if comp.find("butyl")==-1:
comp = comp.replace("tyl-1", "tane-1")
if comp.find("tetra")==-1 and comp.find("tetrazole")!=1: #maintain quaternary ammonium
if comp.find("ammonium")!=-1:
if comp[:comp.index("ammonium")+8]!=comp:
comp = comp.replace("ammonium", "amino")
for old, new in [["ammonium", "amine"], ["pyridinio", "pyridyl"], ["imidazolium", "imidazole"], ["azepanium","azepane"], ["metforminium","metformin"]]: #exceptions
comp = comp.replace(old, new)
for ium in ["5,11-di-dium", "1,10-dium", "1,10-diium", "10-ium", "11-ium", "21-ium"]:
comp = comp.replace(ium, "1-ium")
for ium in ["'-di-ium", "-di-ium", "di-ium", "'''-diium", "'-diium", "-diium", "diium", "'-dium"]:
comp = comp.replace(ium, "-dium")
if comp.find("-dium")!=-1 and comp[comp.find("-dium")-1].isdigit(): #remove -#,#-dium
if comp[comp.index("-dium")-5]=="e":
comp = comp[:comp.index("-dium")-4] + comp[comp.index("-dium")+5:]
else:
comp = comp[:comp.index("-dium")-4] + "e" + comp[comp.index("-dium")+5:]
else:
comp = comp.replace("-dium", "")
if comp.find("-ium")!=-1 and comp[comp.find("-ium")-1].isdigit(): #remove -#-ium
if comp[comp.index("-ium")-3]=="e":
comp = comp[:comp.index("-ium")-2] + comp[comp.index("-ium")+4:]
else:
comp = comp[:comp.index("-ium")-2] + "e" + comp[comp.index("-ium")+4:]
comp = comp.replace("inium", "ine")
comp = comp.replace("ium", "ine")
comp = comp.replace("pyridyle", "pyridyl")
for old,new in [["diguanidine","guanidine"], ["dipyridine","pyridine"], ["dipyrazine", "pyrazine"], ["dipiperidine", "piperidine"], ["tetrapyridinium", "pyridine"], ["hexapyridine", "pyridine"], ["tripiperidine", "piperidine"], ["triguanidine", "guanidine"], ["tetraguanidinium", "guanidine"], ["tetrapiperidine", "piperidine"], ["tetrapiperidinium", "piperidine"], ["dodeca-guanidine", "guanidine"], ["dipyrrolide", "pyrrol"], ["bipyridine", "pyridine"], ["dipyrazoline", "pyrazole"]]: #remove extra prefixes
if comp==old:
comp = new
for find,replace in [["aguanidine","guanidine"], ["tetraethylenepent","tetraethylenepentamine"], ["apiperidine", "piperidine"], ["triethylenetetra", "triethylenetetra-amine"], ["dipiperaziN", "piperazine"]]:
if comp.find(find)!=-1:
comp = replace
return comp
#helper functions to edit name
def substitutions(m):
substitutions_df = pd.read_table("Outputs/01 CCDC Smiles File/Input Files/Substitutions.tsv")
olds = substitutions_df['Old'].tolist()
fix = substitutions_df['Fix'].tolist()
for old in olds:
m = m.replace(old, fix[olds.index(old)])
return m
def typos(m):
typos_df = pd.read_table("Outputs/01 CCDC Smiles File/Input Files/Problematic_structures - Errors.tsv")
typos = typos_df['Error'].tolist()
fix = typos_df['Fix'].tolist()
for typo in typos:
m = m.replace(typo, fix[typos.index(typo)])
return m
def combine(m):
for split in ["(arsenate)","dimethylammonium)-", "cane)-dec"]: #split molecule fragments
if m.find(split)!=-1:
m = m[:m.find(")")+1]
for split in ["hexane)-", "octane)-", ")decane)-", "ium))-"]:
if m.find(split)!=-1:
m = m[:m.find(split[:-1])+(len(split)-1)]
if m.find("ium)(mu")!=-1:
m = m[:m.find("ium)(mu")+4]
for find,replace in [["5 !", "5"], ["ium)-hexa", "ium) hexa"], ["ium)-tetra", "ium) tetra"], ["benzene)-tris", "benzene) tris"]]:
m = m.replace(find, replace)
if m.find("n-oxide)")!=-1: #combine molecule fragments
m = m[:m.index("n-oxide)")-1] + "-" + m[m.index("n-oxide)"):]
if m.find("oxyl 3")!=-1:
m = m[:m.index("oxyl 3")+4] + "-" + m[m.index("oxyl 3")+5:]
for close in ["diphenylamine perchlorate","hydrogen boro", "amine oxide", "2-aminopyridine n", "nyl) s","-yl) s", "yl) ethylene", "sulfonic acid", "acid deoxy", "nium n-o", "16) ox", "methyl ammonium", "diium octaphyrin", "-ium n", "dine 3-", "ine 1-"]:
while m.find(close)!=-1:
stop = close.index(" ")
m = m[:m.index(close)+len(m[:stop])] + m[m.index(close)+len(m[:stop])+1:]
return m
def manual_smiles(clean):
problems_df = pd.read_table("Outputs/01 CCDC Smiles File/Input Files/organics_from_structures.tsv", engine='python', error_bad_lines=False)
problems = problems_df['Amine'].tolist()
smiles = problems_df['SMILES'].tolist()
for problem in problems:
if str(clean)==problem:
clean = smiles[problems.index(problem)]
return clean
def clean_name(crystal, exclusion_files=False):
"""takes a crystal and returns the clean amine name
>>>clean_name("catena-((mu!12$-silicato)-octakis(mu!3$-oxo)-octadecakis(mu!2$-oxo)-dodecaoxo-dodeca-molybdenum-di-vanadium hemikis(triethylamine) bis(ethylenediamine) tetrahydrate) )")
> [ethylenediamine, triethylamine]
>>>clean_name("tris(Metforminium) bis(mu!6$-oxo)-tetrakis(mu!3$-oxo)-tetradecakis(mu!2$-oxo)-octa-oxo-deca-vanadium(v) octahydrate")
> [amino[(diaminomethylene)amino]-n,n-dimethylmethaniminium]
>>>clean_name("catena-((Dimethylammonium)-hexakis(mu!2$-formato-O,O')-iron(iii)-iron(ii))")
> [dimethylamine]
>>>clean_name("catena-(Triethylenetetra-ammonium tetrakis(mu!3$-hydrogen phosphato-O,O',O'')-bis(mu!2$-hydrogen phosphato-O,O')-tetra-zinc)")
> [triethylenetetra-amine]
>>>clean_name("3-Azido-1,2,4-triazolium nitrate")
> []
>>>clean_name("rctt-1,3-bis(4-Pyridinio)-2,4-bis(2-pyridinio)cyclobutane bis(mu!5$-oxo)-tetrakis(mu!3$-oxo)-hexakis(mu!2$-oxo)-tetradecaoxo-octa-molybdenum")
> [1,3-bis(4-pyridinio)-2,4-bis(2-pyridinio)cyclobutane]
>>>clean_name("2,7,11,18-Tetraethyl-3,6,12,17-tetramethylcorrphycene diperchlorate")
> [2,7,11,18-tetraethyl-3,6,12,17-tetramethylcorrphycene]
>>>clean_name("catena-[(1-methylhydrazin-1-ium) tris(mu-formato)-iron(ii)]")
> [1-methylhydrazine]
>>>clean_name("1,2-bis(2'-Tetrahydropyrimidyl)ethane dihydrogenpyrophosphate monohydrate")
> [1,2-bis(2'-tetrahydropyrimidyl)ethane]
>>>clean_name("N-t-Butyl-retinylideniminium perchlorate")
> [N-t-butyl-reTinylideniminium]
>>>clean_name("catena-[bis(trans-1,3-bis(Ammoniomethyl)cyclohexane)-tetrakis(mu!3$-phosphonato)-tri-zinc]")
> [1,3-bis(aminomethyl)cyclohexane]
>>>clean_name("tris(ethane-1,2-diaminium) bis(mu!9$-arsenato)-hexatriacontakis(mu!2$-oxo)-octadecaoxo-octadeca-tungsten ethylenediamine solvate nonahydrate")
> [ethane-1,2-diamine]
>>>clean_name("catena-((mu!2$-Ethylenediamine)-nonakis(mu!2$-oxo)-hexa-boron)")
> []
>>>clean_name("tris(Piperazindium) hexakis(mu!3$-hydroxo)-(mu!2$-hydroxo)-pentakis(mu!2$-oxo)-dodecaoxo-chromium-hexa-molybdenum disulfate dodecahydrate")
> [piperazine]
>>>clean_name("3,6,7-triamino-7H-[1,2,4]triazolo[4,3-b][1,2,4]triazol-2-ium perchlorate")
> [3,6,7-triamino-7h-[1,2,4]triazolo[4,3-b][1,2,4]triazole]
>>>clean_name("4,4'-Bipyridyl phenylphosphonic acid")
> [4,4'-bipyridyl]
>>>clean_name("2,2'-quinoxalin-2,3-diyldipyridinium dinitrate")
> []
>>>clean_name("catena-(bis(1-Aminomethyl-7-aza-1,3,5-azoniatricyclo(3.3.1.1$3,7!)decane)-(mu!2$-oxo)-hexacosaoxooctamolybdate hexahydrate)")
> [bis(1-aminomethyl-7-aza-1,3,5-azatricyclo(3.3.1.13,7)decane)]
>>>clean_name("N,N'-Diphenylanthraquinonedi-iminium diperchlorate chlorobenzene trifluoroacetic acid solvate")
> [c1ccc(cc1)N=C2c3ccccc3C(=Nc4ccccc4)c5ccccc25]
>>>clean_name("catena-(Ethylene-1,2-diammonium tetrakis(mu!4$-phosphato)-bis(mu!3$-oxo)-(mu!2$-fluoro)-penta-gallium clathrate)")
> [1,2-diaminoethylene]
>>>clean_name("hexakis(Imidazolium) hexakis((mu!3$-oxo)-(mu!2$-oxo)-dioxo-tungsten)-tellurate imidazole solvate")
> [imidazole]
"""
if exclusion_files:
directory = "Outputs/01 CCDC Smiles File/exclusion_files/"
met = open(directory + "not_metal.tsv", "a")
metal_file = csv.writer(met, delimiter = '\t')
org = open(directory + "is_strict_organic.tsv", "a")
organic_file = csv.writer(org, delimiter = '\t')
am = open(directory + "is_amine.tsv", "a")
amines_file = csv.writer(am, delimiter = '\t')
sim = open(directory + "is_simple_amine.tsv", "a")
simple_file = csv.writer(sim, delimiter = '\t')
fixed=set()
crysta = crystal.lower()
cryst = combine(crysta)
crys = typos(cryst)
for c in crys.split(): #split molecules by spaces
m = substitutions(c)
if not_metal(m): #remove metals
if is_strict_organic(m): #strict organic
if is_amine(m): #check amine
ref = reformat(m) #clean
n = neutralize(ref) #convert to neutral form
final = manual_smiles(n)
if final!="amine" and final!="diamine" and final!="bisamine" and final!="triamine" and final!="tetramine" and final!="ammonia" and final!="hydrazine":
if str(final).find("a-amine")==-1 or str(final).find("ethyl")!=-1:
fixed.add(final) #account for multiple possible amine
else:
if exclusion_files:
simple_file.writerow([crystal,final])
else:
if exclusion_files:
amines_file.writerow([crystal,m])
else:
if exclusion_files:
organic_file.writerow([crystal,m])
else:
if exclusion_files:
metal_file.writerow([crystal,m])
end = list(set(fixed)) #remove duplicates
if exclusion_files:
met.close()
org.close()
am.close()
sim.close()
return end
def clean_crystal(crystal_file, file_name, exclusion_files=False):
"""takes a file of crystals and returns a file of the refcodes, crystals, and clean amine names"""
crystal_df = pd.read_table(crystal_file) #read file
index = crystal_df['[REFCODE]'].tolist()
crystal_df.set_index('[REFCODE]', inplace=True)
prior = "x" #initialize
for code in crystal_df.index.tolist(): #remove duplicates
if code[:6]==prior:
crystal_df.drop(code, inplace=True)
prior = code[:6]
#crystal_df = crystal_df.iloc[582:583]
if exclusion_files:
crystal_df['Amine']= crystal_df['[_chemical_name_systematic]'].apply(clean_name, exclusion_files=True)
else:
crystal_df['Amine']= crystal_df['[_chemical_name_systematic]'].apply(clean_name)
final_df = crystal_df.loc[:, ['[_chemical_name_systematic]', 'Amine']] #simplify dataframe
#print(final_df)
filename = "Outputs/01 CCDC Smiles File/" + file_name
final_df.to_csv(filename, sep='\t') #save as tsv
Convert the amine name to smiles strings
import urllib.request
import urllib.error
import urllib.parse
from rdkit import Chem
#helper functions to edit smiles strings
def cactus(name):
"""takes an amine name and calls a smiles string from cactus"""
final = set()
for amine in name:
compliant_name = urllib.parse.quote_plus(amine)
url = "https://cactus.nci.nih.gov/chemical/structure/"+amine+"/"+"smiles"
req = urllib.request.Request(url)
try: resp = urllib.request.urlopen(req)
except urllib.error.URLError as e:
resp = None
if resp=="" or resp==None:
resp = amine
else:
resp = resp.read().decode(resp.headers.get_content_charset() or 'utf-8')
if resp.find("!")!=-1: #cannot convert from rdkit smiles back to cactus smiles
resp = amine
final.add(resp)
end = list(set(final)) #remove duplicates
return end
def check_name_cactus(name):
"""takes an amine name and returns a neutralized and canonicalized smiles string"""
name_list = eval(name)
resp = cactus(name_list) #pull name from cactus
neutralize = []
for amine in resp:
amine = amine.replace("[N-]", "[NH]")
amine = amine.replace("[n-]", "[nH]")
for salt in [".[Cl-]", ".[H+]", "[H+].", "[Cl-].", ".Cl", "H+", "H2+", "H3+"]: #clear salt and final neutralize
amine = amine.replace(salt, "")
amine = amine.replace("[N]", "N") #clean
amine = amine.replace("[n]", "n")
mol = Chem.MolFromSmiles(amine) #canonicalize
if mol!=None:
amine = Chem.MolToSmiles(mol, isomericSmiles=False)
neutralize.append(amine)
final = cactus(neutralize) #re-run through cactus to clean
return final
def smiles_file(amines_file, file_name):
"""takes a file of crystals and amine names and returns a file of the refcodes and clean smiles strings"""
smiles_df = pd.read_table(amines_file) #read file
#smiles_df = smiles_df.iloc[5353:5354]
clean_df = smiles_df.loc[smiles_df['Amine'] != '[]'] #exlude empty lists
clean_df['smiles'] = clean_df['Amine'].apply(check_name_cactus) #convert to smiles
repeat_df = pd.DataFrame(clean_df.set_index('[REFCODE]').smiles.apply(pd.Series).stack().reset_index(level=-1, drop=True)) #split crystal amines with repeating REFCODE
merge_df = pd.merge(repeat_df, clean_df, left_index=True, right_on='[REFCODE]').drop("smiles", axis=1).rename(columns={0:'smiles'}) #reformat columns after split
inchi_df = pd.read_table("Outputs/01 CCDC Smiles File/Input Files/standardize_names.tsv") #read file
final_df = pd.merge(merge_df, inchi_df, on="smiles",how='outer').set_index('[REFCODE]')
final_df.drop(['source', 'stdinchi'], axis=1, inplace=True)
filename = "Outputs/01 CCDC Smiles File/" + file_name
final_df.to_csv(filename, sep='\t') #save as tsv
def ccdc_smiles_file(amine_list, smiles=True, exclusion_files=False):
"""takes a list of amine names and creates files of the cleaned amines and smiles strings"""
for amine in amine_list:
if amine=="oxides":
exclusion_files=True
clean_crystal("Data/CCDC/"+amine+" full.tab", "Amines_"+amine.replace(" ", "")+".tsv", exclusion_files)
if smiles:
smiles_file("Outputs/01 CCDC Smiles File/"+"Amines_"+amine.replace(" ", "")+".tsv", "Amines_"+amine.replace(" ", "")+"_smiles.tsv")
ccdc_smiles_file(["oxides", "metal oxides", "metal borates", "metal formates", "metal halides", "metal phosphonates", "metal peroxides", "metal oxalates"])